---
title: LiteSVM
description: Write tests for Solana programs in Rust, TS/JS or Python using LiteSVM.
---

## Overview

[`litesvm`](https://github.com/LiteSVM/litesvm) is a fast and lightweight library for testing Solana programs.
It works by creating an in-process Solana VM optimized for program developers.
This makes it much faster to run and compile than alternatives like `solana-program-test` and `solana-test-validator`.
`litesvm` is available in Rust, TS/JS and Python (as part of the [`solders`](https://pypi.org/project/solders/) library).

### Installation

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

<Tabs items={["Rust", "TS/JS", "Python"]}>
```sh tab="Rust"
cargo add litesvm --dev
```

```sh tab="TS/JS"
npm i litesvm -D
```

```sh tab="Python"
uv add solders # from solders import litesvm
```

</Tabs>

### Minimal Example

<Tabs items={["Rust", "TypeScript", "Python"]}>

```rust tab="Rust"
use litesvm::LiteSVM;
use solana_message::Message;
use solana_pubkey::Pubkey;
use solana_system_interface::instruction::transfer;
use solana_keypair::Keypair;
use solana_signer::Signer;
use solana_transaction::Transaction;

let from_keypair = Keypair::new();
let from = from_keypair.pubkey();
let to = Pubkey::new_unique();

let mut svm = LiteSVM::new();
svm.airdrop(&from, 10_000).unwrap();

let instruction = transfer(&from, &to, 64);
let tx = Transaction::new(
    &[&from_keypair],
    Message::new(&[instruction], Some(&from)),
    svm.latest_blockhash(),
);
let tx_res = svm.send_transaction(tx).unwrap();

let from_account = svm.get_account(&from);
let to_account = svm.get_account(&to);
assert_eq!(from_account.unwrap().lamports, 4936);
assert_eq!(to_account.unwrap().lamports, 64);
```

```ts tab="TypeScript"
import { LiteSVM } from "litesvm";
import {
  PublicKey,
  Transaction,
  SystemProgram,
  Keypair,
  LAMPORTS_PER_SOL,
} from "@solana/web3.js";

test("one transfer", () => {
  const svm = new LiteSVM();
  const payer = new Keypair();
  svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL));
  const receiver = PublicKey.unique();
  const blockhash = svm.latestBlockhash();
  const transferLamports = 1_000_000n;
  const ixs = [
    SystemProgram.transfer({
      fromPubkey: payer.publicKey,
      toPubkey: receiver,
      lamports: transferLamports,
    }),
  ];
  const tx = new Transaction();
  tx.recentBlockhash = blockhash;
  tx.add(...ixs);
  tx.sign(payer);
  svm.sendTransaction(tx);
  const balanceAfter = svm.getBalance(receiver);
  expect(balanceAfter).toBe(transferLamports);
});
```

```python tab="Python"
from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.system_program import transfer
from solders.transaction import VersionedTransaction


def test_transfer() -> None:
    receiver = Pubkey.new_unique()
    client = LiteSVM()
    payer = Keypair()
    client.airdrop(payer.pubkey(), 1_000_000_000)
    blockhash = client.latest_blockhash()
    transfer_lamports = 1_000_000
    ixs = [
        transfer(
            {
                "from_pubkey": payer.pubkey(),
                "to_pubkey": receiver,
                "lamports": transfer_lamports,
            }
        )
    ]
    msg = Message.new_with_blockhash(ixs, payer.pubkey(), blockhash)
    tx = VersionedTransaction(msg, [payer])
    client.send_transaction(tx)
    balance_after = client.get_balance(receiver)
    assert balance_after == transfer_lamports
```

</Tabs>

## Deploying Programs

Most of the time we want to do more than just mess around with token transfers -
we want to test our own programs.

**Tip**: if you want to pull a Solana program from mainnet or devnet, use the `solana program dump` command from the Solana CLI.

To add a compiled program to our tests we can use [`.add_program_from_file`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.add_program_from_file).

Here's an example using a [simple program](https://github.com/solana-labs/solana-program-library/tree/bd216c8103cd8eb9f5f32e742973e7afb52f3b81/examples/rust/logging)
from the Solana Program Library that just does some logging:

<Tabs items={["Rust", "TypeScript", "Python"]}>

```rust tab="Rust"
use {
    litesvm::LiteSVM,
    solana_instruction::{account_meta::AccountMeta, Instruction},
    solana_keypair::Keypair,
    solana_pubkey::{pubkey, Pubkey},
    solana_message::{Message, VersionedMessage},
    solana_signer::Signer,
    solana_transaction::VersionedTransaction,
};

fn test_logging() {
    let program_id = pubkey!("Logging111111111111111111111111111111111111");
    let account_meta = AccountMeta {
        pubkey: Pubkey::new_unique(),
        is_signer: false,
        is_writable: true,
    };
    let ix = Instruction {
        program_id,
        accounts: vec![account_meta],
        data: vec![5, 10, 11, 12, 13, 14],
    };
    let mut svm = LiteSVM::new();
    let payer = Keypair::new();
    let bytes = include_bytes!("../../node-litesvm/program_bytes/spl_example_logging.so");
    svm.add_program(program_id, bytes);
    svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
    let blockhash = svm.latest_blockhash();
    let msg = Message::new_with_blockhash(&[ix], Some(&payer.pubkey()), &blockhash);
    let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap();
    // let's sim it first
    let sim_res = svm.simulate_transaction(tx.clone()).unwrap();
    let meta = svm.send_transaction(tx).unwrap();
    assert_eq!(sim_res.meta, meta);
    assert_eq!(meta.logs[1], "Program log: static string");
    assert!(meta.compute_units_consumed < 10_000) // not being precise here in case it changes
}
```

```ts tab="TypeScript"
import { LiteSVM, TransactionMetadata } from "litesvm";
import {
  Keypair,
  LAMPORTS_PER_SOL,
  PublicKey,
  Transaction,
  TransactionInstruction,
} from "@solana/web3.js";

test("spl logging", () => {
  const programId = PublicKey.unique();
  const svm = new LiteSVM();
  svm.addProgramFromFile(programId, "program_bytes/spl_example_logging.so");
  const payer = new Keypair();
  svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL));
  const blockhash = svm.latestBlockhash();
  const ixs = [
    new TransactionInstruction({
      programId,
      keys: [
        { pubkey: PublicKey.unique(), isSigner: false, isWritable: false },
      ],
    }),
  ];
  const tx = new Transaction();
  tx.recentBlockhash = blockhash;
  tx.add(...ixs);
  tx.sign(payer);
  // let's sim it first
  const simRes = svm.simulateTransaction(tx);
  const sendRes = svm.sendTransaction(tx);
  if (sendRes instanceof TransactionMetadata) {
    expect(simRes.meta().logs()).toEqual(sendRes.logs());
    expect(sendRes.logs()[1]).toBe("Program log: static string");
  } else {
    throw new Error("Unexpected tx failure");
  }
});

```

```python tab="Python"
from pathlib import Path

from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
from solders.transaction_metadata import TransactionMetadata


def test_logging() -> None:
    program_id = Pubkey.from_string("Logging111111111111111111111111111111111111")
    ix = Instruction(
        program_id,
        bytes([5, 10, 11, 12, 13, 14]),
        [AccountMeta(Pubkey.new_unique(), is_signer=False, is_writable=True)],
    )
    client = LiteSVM()
    payer = Keypair()
    client.add_program_from_file(
        program_id, Path("tests/fixtures/spl_example_logging.so")
    )
    client.airdrop(payer.pubkey(), 1_000_000_000)
    blockhash = client.latest_blockhash()
    msg = Message.new_with_blockhash([ix], payer.pubkey(), blockhash)
    tx = VersionedTransaction(msg, [payer])
    # let's sim it first
    sim_res = client.simulate_transaction(tx)
    meta = client.send_transaction(tx)
    assert isinstance(meta, TransactionMetadata)
    assert sim_res.meta() == meta
    assert meta.logs()[1] == "Program log: static string"
    assert (
        meta.compute_units_consumed() < 10_000
    )  # not being precise here in case it changes
```

</Tabs>

## Time travel

Many programs rely on the `Clock` sysvar: for example, a mint that doesn't become available until after
a certain time. With `litesvm` you can dynamically overwrite the `Clock` sysvar
using [`svm.set_sysvar::<Clock>()`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.set_sysvar)
(or `.setClock` in TS, or `.set_clock` in Python).
Here's an example using a program that panics if `clock.unix_timestamp` is greater than 100
(which is on January 1st 1970):

<Tabs items={["Rust", "TypeScript", "Python"]}>
```rust tab="Rust"
use {
    litesvm::LiteSVM,
    solana_clock::Clock,
    solana_instruction::Instruction,
    use solana_keypair::Keypair,
    solana_message::{Message, VersionedMessage},
    solana_pubkey::Pubkey,
    solana_signer::Signer,
    solana_transaction::VersionedTransaction,
};

fn test_set_clock() {
    let program_id = Pubkey::new_unique();
    let mut svm = LiteSVM::new();
    let bytes = include_bytes!("../../node-litesvm/program_bytes/litesvm_clock_example.so");
    svm.add_program(program_id, bytes);
    let payer = Keypair::new();
    let payer_address = payer.pubkey();
    svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
    let blockhash = svm.latest_blockhash();
    let ixs = [Instruction {
        program_id,
        data: vec![],
        accounts: vec![],
    }];
    let msg = Message::new_with_blockhash(&ixs, Some(&payer_address), &blockhash);
    let versioned_msg = VersionedMessage::Legacy(msg);
    let tx = VersionedTransaction::try_new(versioned_msg, &[&payer]).unwrap();
    // set the time to January 1st 2000
    let mut initial_clock = svm.get_sysvar::<Clock>();
    initial_clock.unix_timestamp = 1735689600;
    svm.set_sysvar::<Clock>(&initial_clock);
    // this will fail because it's not January 1970 anymore
    svm.send_transaction(tx).unwrap_err();
    // so let's turn back time
    let mut clock = svm.get_sysvar::<Clock>();
    clock.unix_timestamp = 50;
    svm.set_sysvar::<Clock>(&clock);
    let ixs2 = [Instruction {
        program_id,
        data: vec![1], // unused, this is just to dedup the transaction
        accounts: vec![],
    }];
    let msg2 = Message::new_with_blockhash(&ixs2, Some(&payer_address), &blockhash);
    let versioned_msg2 = VersionedMessage::Legacy(msg2);
    let tx2 = VersionedTransaction::try_new(versioned_msg2, &[&payer]).unwrap();
    // now the transaction goes through
    svm.send_transaction(tx2).unwrap();
}

```

```ts tab="TypeScript"
import {
  FailedTransactionMetadata,
  LiteSVM,
  TransactionMetadata,
} from "litesvm";
import {
  Keypair,
  LAMPORTS_PER_SOL,
  PublicKey,
  Transaction,
  TransactionInstruction,
} from "@solana/web3.js";

test("clock", () => {
  const programId = PublicKey.unique();
  const svm = new LiteSVM();
  svm.addProgramFromFile(programId, "program_bytes/litesvm_clock_example.so");
  const payer = new Keypair();
  svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL));
  const blockhash = svm.latestBlockhash();
  const ixs = [
    new TransactionInstruction({ keys: [], programId, data: Buffer.from("") }),
  ];
  const tx = new Transaction();
  tx.recentBlockhash = blockhash;
  tx.add(...ixs);
  tx.sign(payer);
  // set the time to January 1st 2000
  const initialClock = svm.getClock();
  initialClock.unixTimestamp = 1735689600n;
  svm.setClock(initialClock);
  // this will fail because the contract wants it to be January 1970
  const failed = svm.sendTransaction(tx);
  if (failed instanceof FailedTransactionMetadata) {
    expect(failed.err().toString()).toContain("ProgramFailedToComplete");
  } else {
    throw new Error("Expected transaction failure here");
  }
  // so let's turn back time
  const newClock = svm.getClock();
  newClock.unixTimestamp = 50n;
  svm.setClock(newClock);
  const ixs2 = [
    new TransactionInstruction({
      keys: [],
      programId,
      data: Buffer.from("foobar"), // unused, just here to dedup the tx
    }),
  ];
  const tx2 = new Transaction();
  tx2.recentBlockhash = blockhash;
  tx2.add(...ixs2);
  tx2.sign(payer);
  // now the transaction goes through
  const success = svm.sendTransaction(tx2);
  expect(success).toBeInstanceOf(TransactionMetadata);
});

```

```python tab="Python"
from pathlib import Path

from solders.instruction import Instruction
from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
from solders.transaction_metadata import FailedTransactionMetadata, TransactionMetadata


def test_set_clock() -> None:
    program_id = Pubkey.new_unique()
    client = LiteSVM()
    client.add_program_from_file(
        program_id, Path("tests/fixtures/solders_clock_example.so")
    )
    payer = Keypair()
    client.airdrop(payer.pubkey(), 1_000_000_000)
    blockhash = client.latest_blockhash()
    ixs = [Instruction(program_id=program_id, data=b"", accounts=[])]
    msg = Message.new_with_blockhash(ixs, payer.pubkey(), blockhash)
    tx = VersionedTransaction(msg, [payer])
    # set the time to January 1st 2000
    initial_clock = client.get_clock()
    initial_clock.unix_timestamp = 1735689600
    client.set_clock(initial_clock)
    # this will fail because it's not January 1970 anymore
    bad_res = client.send_transaction(tx)
    assert isinstance(bad_res, FailedTransactionMetadata)
    # so let's turn back time
    clock = client.get_clock()
    clock.unix_timestamp = 50
    client.set_clock(clock)
    ixs2 = [
        Instruction(
            program_id=program_id,
            data=b"foobar",  # unused, this is just to dedup the transaction
            accounts=[],
        )
    ]
    msg2 = Message.new_with_blockhash(ixs2, payer.pubkey(), blockhash)
    tx2 = VersionedTransaction(msg2, [payer])
    # now the transaction goes through
    good_res = client.send_transaction(tx2)
    assert isinstance(good_res, TransactionMetadata)

```
</Tabs>

See also: [`warp_to_slot`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.warp_to_slot), which lets you jump to a future slot.

## Writing arbitrary accounts

LiteSVM lets you write any account data you want, regardless of
whether the account state would even be possible.

Here's an example where we give an account a bunch of USDC,
even though we don't have the USDC mint keypair. This is
convenient for testing because it means we don't have to
work with fake USDC in our tests:

<Tabs items={["Rust", "TypeScript", "Python"]}>
```rust tab="Rust"
use {
    litesvm::LiteSVM,
    solana_account::Account,
    solana_program_option::COption,
    solana_program_pack::Pack,
    solana_pubkey::{pubkey, Pubkey},
    spl_associated_token_account_client::address::get_associated_token_address,
    spl_token::{
        state::{Account as TokenAccount, AccountState},
        ID as TOKEN_PROGRAM_ID,
    },
};

fn test_infinite_usdc_mint() {
    let owner = Pubkey::new_unique();
    let usdc_mint = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
    let ata = get_associated_token_address(&owner, &usdc_mint);
    let usdc_to_own = 1_000_000_000_000;
    let token_acc = TokenAccount {
        mint: usdc_mint,
        owner: owner,
        amount: usdc_to_own,
        delegate: COption::None,
        state: AccountState::Initialized,
        is_native: COption::None,
        delegated_amount: 0,
        close_authority: COption::None,
    };
    let mut svm = LiteSVM::new();
    let mut token_acc_bytes = [0u8; TokenAccount::LEN];
    TokenAccount::pack(token_acc, &mut token_acc_bytes).unwrap();
    svm.set_account(
        ata,
        Account {
            lamports: 1_000_000_000,
            data: token_acc_bytes.to_vec(),
            owner: TOKEN_PROGRAM_ID,
            executable: false,
            rent_epoch: 0,
        },
    )
    .unwrap();
    let raw_account = svm.get_account(&ata).unwrap();
    assert_eq!(
        TokenAccount::unpack(&raw_account.data).unwrap().amount,
        usdc_to_own
    )
}

```

```ts tab="TypeScript"
import { LiteSVM } from "litesvm";
import { PublicKey } from "@solana/web3.js";
import {
  getAssociatedTokenAddressSync,
  AccountLayout,
  ACCOUNT_SIZE,
  TOKEN_PROGRAM_ID,
} from "@solana/spl-token";

test("infinite usdc mint", () => {
  const owner = PublicKey.unique();
  const usdcMint = new PublicKey(
    "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  );
  const ata = getAssociatedTokenAddressSync(usdcMint, owner, true);
  const usdcToOwn = 1_000_000_000_000n;
  const tokenAccData = Buffer.alloc(ACCOUNT_SIZE);
  AccountLayout.encode(
    {
      mint: usdcMint,
      owner,
      amount: usdcToOwn,
      delegateOption: 0,
      delegate: PublicKey.default,
      delegatedAmount: 0n,
      state: 1,
      isNativeOption: 0,
      isNative: 0n,
      closeAuthorityOption: 0,
      closeAuthority: PublicKey.default,
    },
    tokenAccData,
  );
  const svm = new LiteSVM();
  svm.setAccount(ata, {
    lamports: 1_000_000_000,
    data: tokenAccData,
    owner: TOKEN_PROGRAM_ID,
    executable: false,
  });
  const rawAccount = svm.getAccount(ata);
  expect(rawAccount).not.toBeNull();
  const rawAccountData = rawAccount?.data;
  const decoded = AccountLayout.decode(rawAccountData);
  expect(decoded.amount).toBe(usdcToOwn);
});

```

```python tab="Python"
from solders.account import Account
from solders.litesvm import LiteSVM
from solders.pubkey import Pubkey
from solders.token import ID as TOKEN_PROGRAM_ID
from solders.token.associated import get_associated_token_address
from solders.token.state import TokenAccount, TokenAccountState


def test_infinite_usdc_mint() -> None:
    owner = Pubkey.new_unique()
    usdc_mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
    ata = get_associated_token_address(owner, usdc_mint)
    usdc_to_own = 1_000_000_000_000
    token_acc = TokenAccount(
        mint=usdc_mint,
        owner=owner,
        amount=usdc_to_own,
        delegate=None,
        state=TokenAccountState.Initialized,
        is_native=None,
        delegated_amount=0,
        close_authority=None,
    )
    client = LiteSVM()
    client.set_account(
        ata,
        Account(
            lamports=1_000_000_000,
            data=bytes(token_acc),
            owner=TOKEN_PROGRAM_ID,
            executable=False,
        ),
    )
    raw_account = client.get_account(ata)
    assert raw_account is not None
    raw_account_data = raw_account.data
    assert TokenAccount.from_bytes(raw_account_data).amount == usdc_to_own

```
</Tabs>

## Copying Accounts from a live environment

If you want to copy accounts from mainnet or devnet, you can use the `solana account` command in the Solana CLI to save account data to a file.

## Other features

Other things you can do with `litesvm` include:

* Changing the max compute units and other compute budget behaviour using [`.with_compute_budget`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.with_compute_budget).
* Disable transaction signature checking using [`.with_sigverify(false)`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.with_sigverify).
* Find previous transactions using [`.get_transaction`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.get_transaction).

## When should I use `solana-test-validator`?

While `litesvm` is faster and more convenient, it is also less like a real RPC node.
So `solana-test-validator` is still useful when you need to call RPC methods that LiteSVM
doesn't support, or when you want to test something that depends on real-life validator behaviour
rather than just testing your program and client code.

In general though it is recommended to use `litesvm` wherever possible, as it will make your life
much easier.
